/*
 *  Copyright 2008 Mark Ashworth <javameme@gmail.com>.
 * 
 *  Licensed under the GNU General Public License v3 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 * 
 *       http://www.gnu.org/licenses/gpl-3.0.html
 * 
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *  under the License.
 */
package xperiment.metaphor.model.xml;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.List;
import org.dom4j.Attribute;
import org.dom4j.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import xperiment.metaphor.model.DomainObject;
import xperiment.metaphor.model.Persistent;
import metaphor.persistence.Dao;
import metaphor.persistence.xml.PersistentClassResolver;
import metaphor.persistence.filter.Expressions;
import metaphor.persistence.filter.Query;
import metaphor.persistence.xml.ReferenceResolver;

/**
 * The NamedObjectReferenceResolver resolves references by the attribute `name`
 * in the XML element refering to a unique value in the database. For example,
 * the &lt;gender name="MALE" /&gt; would only match to one value in the 
 * database lookup table <i>gender</i>. <br /><br />
 * 
 * <strong>NOTE: 
 * When a name resolves to multiple 
 * instances, the first instance's id is returned with a warn message.</strong>
 * 
 * @author Mark Ashworth <javameme@gmail.com>
 * @version 1.0.0
 */
public class NamedObjectReferenceResolver implements ReferenceResolver {
    protected Logger logger = LoggerFactory.getLogger(getClass());
    /*
     * The data access object to query for persisted instances
     */
    private Dao dao;
    /*
     * Resolver to resolve XML elements to persistent classes 
     */
    private PersistentClassResolver classResolver;
    /**
     * The data access object to query for persisted instances
     * @return Dao
     */
    public Dao getDao() {
        return dao;
    }

    /**
     * The data access object to query for persisted instances
     * @param dao The new value
     */
    public void setDao(Dao dao) {
        this.dao = dao;
    }
    
    /**
     * The persistent class resolver to resolve XML elements to persistent classes
     * @return PersistentClassResolver
     */
    public PersistentClassResolver getClassResolver() {
        return classResolver;
    }

    /**
     * The persistent class resolver to resolve XML elements to persistent classes
     * @param classResolver The new value
     */
    public void setClassResolver(PersistentClassResolver classResolver) {
        this.classResolver = classResolver;
    }
    
    /**
     * Resolves the XML element to a persisted instance
     * @param persistentClass The persisted class that the element represents
     * @param element The XML element
     * @return The serializable id of the persisted instance or null
     */
    @Override
    public void resolve(Element element) {
        if (element == null) {
            throw new IllegalArgumentException("The element is null");
        }
        
        PersistentClassResolver resolver = getClassResolver();
        if (resolver == null) {
            logger.warn("No class resolver specified");
            return;
        } 
          
        Class persistentClass = resolver.resolve(element);
        if (persistentClass == null) {
            logger.warn("The element " + element.asXML() + " did not resolve into a persistent class");
            return;
        }
        
        Serializable id = null;
        Attribute attribute = element.attribute("name");
        if (attribute != null) {
            List results = getDao().retrieve(Query.forClass(persistentClass).add(Expressions.eq("name", attribute.getValue())));
            if (results != null) {
                if (results.size() != 1) {
                    logger.warn("More than 1 object resolved to the name " + attribute.getValue() + ", returning the first result");
                }
                
                Object value = results.get(0);
                if (value instanceof Persistent) {
                    Persistent p = (Persistent) value;
                    id = p.getId();
                } else if (value instanceof DomainObject) {
                    DomainObject p = (DomainObject) value;
                    id = p.getId();
                } else if (value != null) {
                    try {
                        Class clazz = value.getClass();
                        Method method = clazz.getMethod("getId", new Class[]{});
                        Object result = method.invoke(value, new Object[]{});
                        if (result instanceof Serializable) {
                            id = (Serializable) result;
                        } else {
                            logger.warn("Unable to resolve the id of the object " + value);
                        }
                    } catch (Throwable e) {
                        logger.warn("Unable to resolve id of the object", e);
                    }
                }
            }
        }
        
        if (id != null) {    
            attribute = element.attribute("id");
            if (attribute != null) {
                attribute.setValue(id.toString());
            } else {
                element.addAttribute("id", id.toString());
            }
        }
    }

    /**
     * Determines if the XML element is supported by this resolver
     * @param element The ement to determine if it is supported by the resolver
     * @return boolean
     */
    @Override
    public boolean supports(Element element) {
        if (element == null) {
            throw new IllegalArgumentException("The element is null");
        }
        Attribute attribute = element.attribute("name");
        return attribute != null;
    }
}
